package proprietary

import (
	"encoding/binary"

	"github.com/gofrs/uuid"
	"github.com/pkg/errors"

	"github.com/brocaar/chirpstack-api/go/v3/common"
	"github.com/brocaar/chirpstack-api/go/v3/gw"
	"github.com/brocaar/lorawan"
	"network/v3/internal/backend/gateway"
	"network/v3/internal/band"
	"network/v3/internal/helpers"
	"network/v3/internal/storage"
)

var tasks = []func(*proprietaryContext) error{
	sendProprietaryDown,
	saveDownlinkFrames,
}

func sendProprietaryDown(ctx *proprietaryContext) error {
	var txPower int
	if downlinkTXPower != -1 {
		txPower = downlinkTXPower
	} else {
		txPower = band.Band().GetDownlinkTXPower(ctx.Frequency)
	}

	phy := lorawan.PHYPayload{
		MHDR: lorawan.MHDR{
			Major: lorawan.LoRaWANR1,
			MType: lorawan.Proprietary,
		},
		MACPayload: &lorawan.DataPayload{Bytes: ctx.MACPayload},
		MIC:        ctx.MIC,
	}
	phyB, err := phy.MarshalBinary()
	if err != nil {
		return errors.Wrap(err, "marshal phypayload error")
	}

	for _, mac := range ctx.GatewayMACs {
		downID, err := uuid.NewV4()
		if err != nil {
			return errors.Wrap(err, "new uuid error")
		}
		token := binary.BigEndian.Uint16(downID[0:2])

		txInfo := gw.DownlinkTXInfo{
			Frequency: uint32(ctx.Frequency),
			Power:     int32(txPower),

			Timing: gw.DownlinkTiming_IMMEDIATELY,
			TimingInfo: &gw.DownlinkTXInfo_ImmediatelyTimingInfo{
				ImmediatelyTimingInfo: &gw.ImmediatelyTimingInfo{},
			},
		}

		err = helpers.SetDownlinkTXInfoDataRate(&txInfo, ctx.DR, band.Band())
		if err != nil {
			return errors.Wrap(err, "set downlink tx-info data-rate error")
		}

		// for LoRa, set the iPol value
		if txInfo.Modulation == common.Modulation_LORA {
			modInfo := txInfo.GetLoraModulationInfo()
			if modInfo != nil {
				modInfo.PolarizationInversion = ctx.IPol
			}
		}

		df := gw.DownlinkFrame{
			Token:      uint32(token),
			DownlinkId: downID[:],
			GatewayId:  mac[:],
			Items: []*gw.DownlinkFrameItem{
				{
					TxInfo:     &txInfo,
					PhyPayload: phyB,
				},
			},
		}

		ctx.DownlinkFrames = append(ctx.DownlinkFrames, df)

		if err := gateway.Backend().SendTXPacket(df); err != nil {
			return errors.Wrap(err, "send downlink frame to gateway error")
		}
	}

	return nil
}

func saveDownlinkFrames(ctx *proprietaryContext) error {
	for _, df := range ctx.DownlinkFrames {
		if err := storage.SaveDownlinkFrame(ctx.ctx, &storage.DownlinkFrame{
			Token:         df.Token,
			DownlinkFrame: &df,
		}); err != nil {
			return errors.Wrap(err, "save downlink-frame error")
		}
	}

	return nil
}
